package org.truenewx.tnxjeex.fss.service.storage.aws;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import com.amazonaws.SdkClientException;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.*;
import org.apache.commons.lang3.StringUtils;
import org.truenewx.tnxjee.core.Strings;
import org.truenewx.tnxjee.core.util.EncryptUtil;
import org.truenewx.tnxjee.core.util.LogUtil;
import org.truenewx.tnxjee.core.util.StringUtil;
import org.truenewx.tnxjee.core.util.function.TrPredicate;
import org.truenewx.tnxjeex.fss.model.FssFileDetail;
import org.truenewx.tnxjeex.fss.service.FssDirDeletePredicate;
import org.truenewx.tnxjeex.fss.service.storage.FssStorageAccessor;
import org.truenewx.tnxjeex.fss.service.storage.FssStorageProvider;
import org.truenewx.tnxjeex.fss.service.util.FssUtil;

public class AwsFssStorageAccessor implements FssStorageAccessor {

    private AwsAccount account;

    public AwsFssStorageAccessor(AwsAccount account) {
        this.account = account;
    }

    @Override
    public FssStorageProvider getProvider() {
        return FssStorageProvider.AWS;
    }

    private String getBucketName() {
        return this.account.getBucketName();
    }

    @Override
    public void write(InputStream in, String storagePath, String originalFilename) throws IOException {
        ObjectMetadata objectMetadata = new ObjectMetadata();
        if (StringUtils.isNotBlank(originalFilename)) {
            String base64Filename = EncryptUtil.encryptByBase64(originalFilename); // 中文文件名会乱码导致签名校验失败
            objectMetadata.getUserMetadata().put(FssUtil.METADATA_FILENAME, base64Filename);
        }
        Charset charset = FssUtil.getCharset(in);
        if (charset != null) {
            objectMetadata.setContentEncoding(charset.name());
        }
        objectMetadata.setContentLength(in.available());
        String standardizedStoragePath = FssUtil.standardizePath(storagePath);
        this.account.getObjectClient().putObject(getBucketName(), standardizedStoragePath, in, objectMetadata);
    }

    private ObjectMetadata getObjectMetadata(String path) {
        path = FssUtil.standardizePath(path);
        try {
            return this.account.getObjectClient().getObjectMetadata(getBucketName(), path);
        } catch (Exception e) {
            LogUtil.error(getClass(), e);
        }
        return null;
    }

    @Override
    public FssFileDetail getDetail(String storagePath) {
        ObjectMetadata meta = getObjectMetadata(storagePath);
        if (meta != null) {
            String filename = meta.getUserMetadata().get(FssUtil.METADATA_FILENAME);
            if (StringUtils.isNotBlank(filename)) {
                try {
                    filename = EncryptUtil.decryptByBase64(filename);
                } catch (Exception ignored) {
                }
            }
            return new FssFileDetail(filename, meta.getLastModified().getTime(), meta.getContentLength());
        }
        return null;
    }

    @Override
    public Charset getCharset(String storagePath) {
        ObjectMetadata meta = getObjectMetadata(storagePath);
        if (meta != null) {
            String encoding = meta.getContentEncoding();
            if (encoding != null) {
                try {
                    return Charset.forName(encoding);
                } catch (Exception ignored) {
                }
            }
        }
        return null;
    }

    @Override
    public InputStream getReadStream(String storagePath) throws IOException {
        try {
            storagePath = FssUtil.standardizePath(storagePath);
            return this.account.getObjectClient().getObject(getBucketName(), storagePath).getObjectContent();
        } catch (SdkClientException e) {
            throw new IOException(e);
        }
    }

    @Override
    public void delete(String storagePath, FssDirDeletePredicate dirDeletePredicate) {
        AmazonS3 client = this.account.getObjectClient();
        String bucketName = getBucketName();
        String standardizedPath = FssUtil.standardizePath(storagePath);
        try {
            String filename = standardizedPath.substring(standardizedPath.lastIndexOf(Strings.SLASH) + 1);
            // 支持按文件名中的通配符（不支持路径中的）删除
            int index = filename.indexOf(Strings.ASTERISK);
            if (index >= 0) {
                String prefix = standardizedPath.substring(0, index); // 路径中首个通配符之前的部分均作为前缀查找匹配对象
                ListObjectsV2Result result = client.listObjectsV2(bucketName, prefix);
                List<String> deletedKeys = new ArrayList<>();
                for (S3ObjectSummary objectSummary : result.getObjectSummaries()) {
                    String key = objectSummary.getKey();
                    if (StringUtil.wildcardMatch(key, standardizedPath)) {
                        deletedKeys.add(key);
                    }
                }
                if (deletedKeys.size() > 0) {
                    DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(bucketName);
                    deleteObjectsRequest.setKeys(deletedKeys.stream().map(DeleteObjectsRequest.KeyVersion::new)
                            .collect(Collectors.toList()));
                    client.deleteObjects(deleteObjectsRequest);
                }
            } else {
                client.deleteObject(bucketName, standardizedPath);
            }
        } catch (Exception e) {
            LogUtil.warn(getClass(), e);
        }
    }

    @Override
    public void copy(String sourceStoragePath, String targetStoragePath) {
        sourceStoragePath = FssUtil.standardizePath(sourceStoragePath);
        targetStoragePath = FssUtil.standardizePath(targetStoragePath);
        String bucketName = getBucketName();
        this.account.getObjectClient().copyObject(bucketName, sourceStoragePath, bucketName, targetStoragePath);
    }

    @Override
    public void move(String sourceStoragePath, String targetStoragePath) {
        sourceStoragePath = FssUtil.standardizePath(sourceStoragePath);
        targetStoragePath = FssUtil.standardizePath(targetStoragePath);
        AmazonS3 client = this.account.getObjectClient();
        String bucketName = getBucketName();
        client.copyObject(bucketName, sourceStoragePath, bucketName, targetStoragePath);
        client.deleteObject(bucketName, sourceStoragePath);
    }

    @Override
    public long getTotalSize(String storageDir) {
        storageDir = FssUtil.standardizePath(storageDir);
        long total = 0;
        ObjectListing objects = this.account.getObjectClient().listObjects(getBucketName(), storageDir);
        List<S3ObjectSummary> objectSummaries = objects.getObjectSummaries();
        for (S3ObjectSummary objectSummary : objectSummaries) {
            total += objectSummary.getSize();
        }
        return total;
    }

    @Override
    public void loopReadStream(String storageDir, TrPredicate<String, Long, InputStream> predicate) {
        storageDir = FssUtil.standardizePath(storageDir);
        AmazonS3 client = this.account.getObjectClient();
        ObjectListing objects = client.listObjects(getBucketName(), storageDir);
        List<S3ObjectSummary> objectSummaries = objects.getObjectSummaries();
        for (S3ObjectSummary objectSummary : objectSummaries) {
            long size = objectSummary.getSize();
            String storagePath = objectSummary.getKey();
            InputStream in = client.getObject(getBucketName(), storagePath).getObjectContent();
            // 在此调用in.close()会导致后续输入流读取超时
            if (!predicate.test(storagePath, size, in)) {
                break;
            }
        }
    }

}
